//
// Created by xwt
//

#include "coverageParser.hpp"
//#include "ab_utils.hpp"
#include <ab_logger.hpp>
#include <libgen.h>
#include <libgen.h>

namespace anbantech {

/////////////////////////help funcs start//////////////////////////////////
    void coverageParser::fillUnusedLine(int preLine, int curLine, std::vector<int> &ExecutedCounts) {
        if (preLine + 1 != curLine) {
            while (preLine < curLine - 1) {
                ExecutedCounts.push_back(-1);
                preLine++;
            }
        }
    }

    std::string coverageParser::readFileToString(const std::string &filename) {
        std::ifstream file(filename);

        if (!file.is_open()) {
            return "";
        }

        std::ostringstream content_stream;
        content_stream << file.rdbuf();
        return content_stream.str();
    }

    bool coverageParser::validCheck(std::string &name, int lineNumber, std::map<std::string, gcov_function_> &functions) {
        auto it = functions.find(name);
        if (it != functions.end()) {
            gcov_function_ func = it->second;
            if (lineNumber >= func.start_line && lineNumber <= func.end_line) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    std::string coverageParser::pathJoin(const std::vector<std::string> &elem) {
        if (elem.empty()) {
            return "";
        }
        std::string result = elem[0];
        for (int i = 1; i < elem.size(); i++) {
            if (elem[i].empty()) {
                continue;
            }
            if (result.back() != '/' && elem[i].front() != '/') {
                result.append("/");
            }
            if (result.back() == '/' && elem[i].front() == '/') {
                result.pop_back();
            }
            result.append(elem[i]);
        }
        return result;
    }
    std::string coverageParser::getExt(const std::string &filename) {
        size_t pos = filename.find_last_of('.');
        if (pos != std::string::npos) {
            return filename.substr(pos);
        } else {
            return "";
        }
    }
    bool coverageParser::hasPrefix(const std::string &s, const std::string &prefix) {
        if (prefix.length() > s.length()) {
            return false;
        }
        return s.compare(0, prefix.length(), prefix) == 0;
    }
    std::string coverageParser::trimPrefix(const std::string &s, const std::string &prefix) {
        if (prefix.length() > s.length()) {
            return s.substr(0);
        }
        size_t pos = s.find(prefix);
        if (pos == 0) {
            return s.substr(prefix.length());
        } else {
            return s.substr(0);
        }
    }
    std::string coverageParser::replaceExt(const std::string &path, const std::string &newExt) {
        std::size_t dotPos = path.find_last_of('.');
        if (dotPos != std::string::npos) {
            return path.substr(0, dotPos) + newExt;
        }
        return path;
    }
    bool coverageParser::fileExist(const std::string &filePath) {
        std::ifstream file(filePath);
        return file.good();
    }
    std::string coverageParser::getDemangledName(std::string &name, std::map<std::string, gcov_function_> &functions) {
        auto it = functions.find(name);
        if (it != functions.end()) {
            return it->second.demangled_name;
        } else {
            return "";
        }
    }
    void coverageParser::fillFunctionLines(int count, std::vector<int> &ExecutedCounts) {
        int index = 0;
        while (index < count) {
            ExecutedCounts.push_back(-1);
            ++index;
        }
    }

    void coverageParser::processFile(const std::string& dir, const std::string& fileName,
                                     std::vector<std::string>& lsGcda, std::vector<std::string>& fileNames) {
        std::string ext = fileName.substr(fileName.find_last_of('.') + 1);
        std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);

        if (ext == "c" || ext == "cxx" || ext == "cpp" || ext == "c++" || ext == "cc" || ext == "cp") {
            std::string base = dir + "/" + fileName.substr(0, fileName.find_last_of('.'));
            std::string gcno = base + ".gcno";
            std::string gcda = base + ".gcda";

            if (fileExist(gcno) && fileExist(gcda)) {
                std::string gc = base + ".gcov.json";
                std::string command = "gcov -b -f -i -t -c " + base + " > " + gc;
//            std::cout << "Running command: " << command << std::endl;

                if (system(command.c_str()) != 0) {
//                std::cerr << "Failed to run the command: " << command << std::endl;
                } else {
                    lsGcda.push_back(gcda);
                    fileNames.push_back(gc);
                }
            }
        }
    }

    void coverageParser::walkDir(const std::string& root, const std::string& dir,
                                 std::vector<std::string>& lsGcda, std::vector<std::string>& fileNames) {
        DIR* dp = opendir(dir.c_str());

        if (!dp) {
            return;
        }

        dirent* entry;
        while ((entry = readdir(dp)) != nullptr) {
            std::string name = entry->d_name;
            if (name != "." && name != "..") {
                std::string filePath = dir + "/" + name;

                if (entry->d_type == DT_DIR) {
                    walkDir(root, filePath, lsGcda, fileNames);
                } else {
                    processFile(dir, name, lsGcda, fileNames);
                }
            }
        }

        closedir(dp);
    }

/////////////////////////help funcs end//////////////////////////////////

    int coverageParser::getJsonFileNames(const std::string &dirctory,std::vector<std::string> &gcdaFiles) {

        std::vector<std::string> lsGcda;

        walkDir(dirctory, dirctory, lsGcda, gcdaFiles);

        for(auto& f:lsGcda){
//        std::remove(f.c_str());
        }

        return 0;
    }

    std::vector<agent_coverage> coverageParser::parseCoverageJson(std::string fileName) {
        std::vector<agent_coverage> coverages;
        std::string content = readFileToString(fileName);
        if (content.empty()) {
            return coverages;
        }
        auto json_start = content.find_first_of("{");
        if (json_start == std::string::npos) {
            return coverages;
        }
        auto json_str = content.substr(json_start);
        json json_gcov = json::parse(json_str);
        gcov_coverage gcov = json_gcov;
        if (gcov.files.empty()) {
            return coverages;
        }
        for (auto &File: gcov.files) {
            // 如果文件名存在，认为该文件是引用到runtime 库，待验证
            if (fileExist(File.file))
                continue;
            agent_coverage cov;
            std::vector<agent_coverage> functionCovList;
            int ret = ParseLocalFileCoverage(File, gcov.data_file,
                                             cov, functionCovList);
            if (ret != 0) {
                return coverages;
            }
            if (!cov.empty()) {
                coverages.push_back(cov);
            }
            for (auto &fun: functionCovList) {
                if (!fun.empty()) {
                    coverages.push_back(fun);
                }
            }
        }
        return coverages;
    }


    int coverageParser::ParseLocalFileCoverage(gcov_file_ &File, std::string &filePath,
            /*out*/agent_coverage &cov, std::vector<agent_coverage> &functionCovList) {
        std::string preFunction;
        std::string curFunction;
        int preLine = 0;
        int curLine = 0;
        char tmp[512]={0};

        std::map<std::string, gcov_function_> allFunctions;
        if(File.functions.empty()){
            return -1;
        }

        for(auto& function:File.functions){
            allFunctions[function.name]=function;
        }
        std::string baseElement;

        snprintf(tmp,512,"%s",filePath.c_str());
        std::string dir=dirname(tmp);
        snprintf(tmp,512,"%s",dir.c_str());
        std::string base= basename(tmp);
        if(hasPrefix(File.file,base)){
            std::string fName= trimPrefix(File.file,"/");
            fName= trimPrefix(fName,base);
            baseElement= pathJoin({dir,fName});
        }else{
            baseElement= pathJoin({dir, File.file});
        }


        snprintf(tmp,512,"%s",filePath.c_str());
        std::string filePathBase=basename(tmp);
        snprintf(tmp,512,"%s",filePath.c_str());
        std::string filePathDir= dirname(tmp);
        snprintf(tmp,512,"%s",File.file.c_str());
        std::string fileNameBase=basename(tmp);

        if(getExt(filePath)==".o"){
            if(replaceExt(filePathBase,"")==fileNameBase){
                std::string truePath;
                int ret = FindTruePathInCMakeMode("",filePathBase,truePath);
                if(ret==0 && !truePath.empty()) {
                    baseElement = trimPrefix(truePath, "/");
                }else{
                    return -1;
                }
            }else{
                return 0;
            }
        }else if(fileExist(baseElement)){
            baseElement = trimPrefix(pathJoin({filePathDir,File.file}),"/src/");
        }else if(fileExist(pathJoin({dir,File.file}))){
            baseElement = pathJoin({dir,File.file});
        }else if(fileExist(File.file)){
            baseElement=File.file;
        }else if(fileExist(filePath)){
            baseElement=filePath;
        }
        else{
            return 0;
        }

        agent_coverage covFunction;
        covFunction.kind="function";
        covFunction.element=baseElement;

        cov.kind="file";
        cov.element=baseElement;

        if(allFunctions.size()==1){
            covFunction.element= pathJoin({baseElement,File.functions[0].demangled_name});
        }

        bool startLine = true;
        cov.lines.total=File.lines.size();

        for(auto &line:File.lines){
            if(line.function_name.empty()){
                continue;
            }
            preFunction=curFunction;
            curFunction=line.function_name;

            if(!validCheck(curFunction,line.line_number,allFunctions)){
                continue;
            }
            if(!preFunction.empty() && !curFunction.empty() && preFunction!=curFunction){
                std::string elementNext = getDemangledName(curFunction, allFunctions);
                std::string elementCur = getDemangledName(preFunction,allFunctions);
                if(elementCur.empty()||elementNext.empty()){
                    return 0;
                }
                fillFunctionLines(allFunctions[preFunction].end_line-curLine,
                                  covFunction.linesInfo.executedCounts);

                elementNext = pathJoin({baseElement, elementNext});
                elementCur = pathJoin({baseElement, elementCur});

                covFunction.element = elementCur;
                functionCovList.push_back(covFunction);

                agent_coverage tmpCov;
                covFunction = tmpCov;
                covFunction.kind="function";
                covFunction.element=elementNext;

                covFunction.linesInfo.executedCounts.push_back(line.count);
            }
            if(curLine > preLine){
                preLine = curLine;
            }
            curLine = line.line_number;

            cov.branches.total += line.branches.size();
            covFunction.branches.total += line.branches.size();
            covFunction.lines.total += 1;

            for (auto &branch:line.branches){
                if(branch.count != 0){
                    cov.branches.hit++;
                    covFunction.branches.hit++;
                }
                int attr=0;
                if(branch.fallthrough){
                    attr=1;
                }
                agent_branchinfo tmpBranchInfo;
                tmpBranchInfo.startLine=line.line_number;
                tmpBranchInfo.startColumn=0;
                tmpBranchInfo.attribute=attr;
                tmpBranchInfo.executedCount=branch.count;
                cov.branchInfoes.push_back(tmpBranchInfo);
                covFunction.branchInfoes.push_back(tmpBranchInfo);
            }
            if(startLine){
                startLine= false;
                cov.linesInfo.start=line.line_number;
                covFunction.linesInfo.start = line.line_number;
                covFunction.linesInfo.executedCounts.push_back(line.count);
            }
            if(line.count){
                cov.lines.hit++;
                covFunction.lines.hit++;
            }
            if(curLine > preLine){
                fillUnusedLine(preLine, curLine, covFunction.linesInfo.executedCounts);
                covFunction.linesInfo.executedCounts.push_back(line.count);

                if(preFunction == curFunction){
                    fillUnusedLine(preLine,curLine,covFunction.linesInfo.executedCounts);
                    covFunction.linesInfo.executedCounts.push_back(line.count);
                }
            }
        }
        functionCovList.push_back(covFunction);
        cov.functions.total = File.functions.size();

        for(auto &function:File.functions){
            if(function.execution_count!=0){
                cov.functions.hit++;
            }
            agent_eleinfo tmpFuncInfo;
            tmpFuncInfo.name=function.demangled_name;
            tmpFuncInfo.startLine = function.start_line;
            tmpFuncInfo.startColumn=function.start_column;
            tmpFuncInfo.endLine=function.end_line;
            tmpFuncInfo.attribute=0;
            tmpFuncInfo.executedCount=function.execution_count;
            cov.functionInfoes.push_back(tmpFuncInfo);
        }
        return 0;
    }

    int coverageParser::FindTruePathInCMakeMode(const std::string& dir, const std::string& target, std::string& truePath) {
        DIR* dp = opendir(dir.c_str());
        if (!dp) {
            return -1;
        }

        struct dirent* entry;
        while ((entry = readdir(dp)) != nullptr) {
            std::string filename = entry->d_name;
            if (filename == "." || filename == "..") {
                continue;
            }

            std::string fullPath = dir + "/" + filename;
            struct stat fileStat;
            if (stat(fullPath.c_str(), &fileStat) == 0) {
                if (S_ISDIR(fileStat.st_mode)) {
                    // Recurse into subdirectories
                    int result = FindTruePathInCMakeMode(fullPath, target, truePath);
                    if (result == 0) {
                        closedir(dp);
                        return 0;
                    }
                } else if (filename == target) {
                    truePath = fullPath;
                    closedir(dp);
                    return 0;
                }
            }
        }

        closedir(dp);
        return 1; // Target not found
    }

    std::vector<agent_coverage> coverageParser::Get(const std::string &gcda_root_path) {
        mDirectory = gcda_root_path;
        std::vector<std::string> fileNames;
        int ret = getJsonFileNames(mDirectory,fileNames);
        if(ret){
            return {};
        }
        std::vector<agent_coverage> coverages;
        agent_coverage mainCov;

        for(auto &fileName:fileNames){
            auto coves = parseCoverageJson(fileName);
            for(auto &cov:coves){
                if(!cov.empty()){
                    sumCov(mainCov,cov);
                }
                coverages.push_back(cov);
            }

//        std::remove(fileName.c_str());
        }
        std::vector<agent_coverage> allcoves;
        allcoves.push_back(mainCov);
        allcoves.insert(allcoves.end(), std::make_move_iterator(coverages.begin()), std::make_move_iterator(coverages.end()));
        return allcoves;
    }

    void coverageParser::sumCov(agent_coverage& x,agent_coverage& cov){
        x.lines.hit+=cov.lines.hit;
        x.branches.hit+=cov.branches.hit;
        x.functions.hit+=cov.functions.hit;
    }


} // anbantech